/*
 * @Author: 王炜皓
 * @Date: 2023-07-29 16:02:11
 * @LastEditTime: 2023-07-29 16:58:57
 * @LastEditors: 王炜皓
 * @Description: 风场
 * @FilePath: \cesium-tool\src\views\test\extend\effect\wind\windEffect.ts
 */

const Cesium = window.Cesium;
import WindyMockData from './WindyMockData';
import { WindyData, WindyParams } from './WindyTypes';
import CanvasParticle from './CanvasParticle';
import CalculateWindyGrids from './CalculateWindyGrids';
import WindyColor from './WindyColor';
import HandlerEvents from './HandlerEvents';
import CanvasDom from './DomUtil';
 
export default class WindyEffect {
  customWindyData: WindyData; // 风场源数据
  viewer: any; // 三维场景;
  canvas: any; // 绘制风场的画布;
  extent: number[]; // 风场绘制时的地图范围，[west, east, south, north]
  canvasContext: any; // canvas上下文
  canvasWidth: number; // 画布宽度
  canvasHeight: number; // 画布高度
  speedRate: number; // 风前进速率，意思是将当前风场横向纵向分成100份，再乘以风速就能得到移动位置，无论地图缩放到哪一级别都是一样的速度，可以用该数值控制线流动的快慢，值越大，越慢，
  particlesNumber: number; // 初始粒子总数，根据实际需要进行调节
  maxAge: number; // 每个粒子的最大生存周期
  frameTime: number; // 每秒刷新次数，因为requestAnimationFrame固定每秒60次的渲染，所以如果不想这么快，就把该数值调小一些
  lineWidth: number; // 线宽度
  initExtent: number[]; // 风场初始范围，[west, east, south, north]
  calc_speedRate: number[]; // 根据speedRate参数计算经纬度步进长度
  windField: CalculateWindyGrids; // 格式化待绘制风场数据;
  particles: CanvasParticle[]; // [];
  animateFrame: any; // requestAnimationFrame事件句柄，用来清除操作
  isdistory: boolean; // 是否销毁，进行删除操作
  windyColor: WindyColor; // 风场线配色;
  canvasDom: CanvasDom;
  hander: any;
  constructor(viewer: any, params: WindyParams) {
    if (!viewer) throw new Error('the viewer is required!');
    this.customWindyData = WindyMockData(); //模拟风场数据
    this.viewer = viewer;
    const viewerContainer = this.viewer.container;
    this.canvasDom = new CanvasDom(viewerContainer);
    this.canvas = this.canvasDom.canvas;
    this.extent = params.extent || [];
    this.canvasContext = this.canvas.getContext('2d');
    this.canvasWidth = viewerContainer.offsetWidth;
    this.canvasHeight = viewerContainer.offsetHeight;
    this.speedRate = params.speedRate || 100;
    this.particlesNumber = params.particlesNumber || 20000;
    this.maxAge = params.maxAge || 120;
    this.frameTime = 1000 / (params.frameRate || 10);
    this.lineWidth = params.lineWidth || 2;
    this.initExtent = [];
    this.calc_speedRate = [0, 0];
    this.windField = new CalculateWindyGrids(this.customWindyData);
    this.particles = [];
    this.animateFrame = null;
    this.isdistory = false;
    this.windyColor = new WindyColor();
    this.init();
    this.hander = new HandlerEvents(this.viewer, this);
  }
 
  init() {
    const $this = this;
    const h = this.windField.header;
    this.initExtent = [h.lng1, h.lng2, h.lat1, h.lat2];
    // 如果风场创建时，传入的参数有extent，就根据给定的extent，让随机生成的粒子落在extent范围内
    if (this.extent.length !== 0) {
      this.extent = [
        Math.max(this.initExtent[0], this.extent[0]),
        Math.min(this.initExtent[1], this.extent[1]),
        Math.max(this.initExtent[2], this.extent[2]),
        Math.min(this.initExtent[3], this.extent[3]),
      ];
    }
    this.calculateStep();
    // 创建风场粒子
    for (let i = 0; i < this.particlesNumber; i++) {
      this.particles.push(this.randomParticle(new CanvasParticle())!);
    }
    this.canvasContext.fillStyle = 'rgba(0, 0, 0, 0.97)';
    this.canvasContext.globalAlpha = 0.6;
    this.animate();
    let time = Date.now();
    const frame = () => {
      if (!$this.isdistory) {
        $this.animateFrame = requestAnimationFrame(frame);
        const now = Date.now();
        const delta = now - time;
        if (delta > $this.frameTime) {
          time = now - (delta % $this.frameTime);
          $this.animate();
        }
      } else {
        $this.removeLines();
      }
    };
    frame();
  }
  /**
   * 根据空间范围计算经纬度步长
   */
  calculateStep() {
    const extent = this.extent.length !== 0 ? this.extent : this.initExtent;
    const speed = this.speedRate;
    this.calc_speedRate = [
      (extent[1] - extent[0]) / speed,
      (extent[3] - extent[2]) / speed,
    ];
  }
 
  /**
   * 根据现有参数重新生成风场
   */
  refresh() {
    window.cancelAnimationFrame(this.animateFrame);
    this.particles = [];
    this.init();
  }
 
  animate() {
    const $this = this;
    const field = $this.windField;
    let nextLng: number;
    let nextLat: number;
    let uv: number[];
    $this.particles.forEach((particle) => {
      if (particle.age <= 0) {
        $this.randomParticle(particle);
      } else {
        const { tlng, tlat } = particle;
        const gridpos = $this.map2Grid(tlng, tlat);
        const tx = gridpos[0];
        const ty = gridpos[1];
        if (!$this.isInExtent(tlng, tlat)) {
          particle.age = 0;
        } else {
          uv = field.getUV(tx, ty);
          nextLng = tlng + $this.calc_speedRate[0] * uv[0];
          nextLat = tlat + $this.calc_speedRate[1] * uv[1];
          particle.lng = tlng;
          particle.lat = tlat;
          particle.x = tx;
          particle.y = ty;
          particle.tlng = nextLng;
          particle.tlat = nextLat;
          particle.age--;
        }
      }
    });
    if ($this.particles.length <= 0) this.removeLines();
    $this.createLine();
  }
 
  /**
   * 粒子是否在地图范围内
   * @param lng 经度
   * @param lat 纬度
   * @returns
   */
  isInExtent(lng: number, lat: number) {
    const extent = this.initExtent;
    return lng >= extent[0] &&
      lng <= extent[1] &&
      lat >= extent[2] &&
      lat <= extent[3]
      ? true
      : false;
  }
  /**
   * 重置画布大小
   */
  resize(w: number, h: number) {
    this.canvasWidth = w;
    this.canvasHeight = h;
  }
 
  removeLines() {
    window.cancelAnimationFrame(this.animateFrame);
    this.isdistory = true;
    this.canvas.width = 1;
  }
 
  /**
   * 根据粒子当前所处的位置(棋盘网格位置)，计算经纬度，在根据经纬度返回屏幕坐标
   */
  map2Pixel(lng: number, lat: number, particle: CanvasParticle) {
    const cartesian = Cesium.Cartesian3.fromDegrees(lng, lat, 0);
    // 判断当前点是否在地球可见端
    const isVisible = new Cesium.EllipsoidalOccluder(
      Cesium.Ellipsoid.WGS84,
      this.viewer.camera.position
    ).isPointVisible(cartesian);
    const pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
      this.viewer.scene,
      cartesian
    );
    if (!isVisible) {
      particle.age = 0;
    }
    return pos ? [pos.x, pos.y] : null;
  }
 
  /**
   * 根据经纬度，计算风场网格位置
   * @returns
   */
  map2Grid(lng: number, lat: number) {
    const field = this.windField;
    const extent = this.initExtent;
    const x =
      ((lng - extent[0]) / (extent[1] - extent[0])) * (field.header.nx - 1);
    const y =
      ((extent[3] - lat) / (extent[3] - extent[2])) * (field.header.ny - 1);
    return [x, y];
  }
  /**
   * 创建（绘制）风场线
   */
  createLine() {
    const $this = this;
    this.canvasContext.lineWidth = $this.lineWidth;
    // 前后绘制的图形如果发生遮挡，显示后绘制图形跟前绘制图形重合区域前绘制的图形部分，
    this.canvasContext.globalCompositeOperation = 'destination-in';
    this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
    this.canvasContext.globalCompositeOperation = 'lighter'; // 重叠部分的颜色会被重新计算
    this.canvasContext.globalAlpha = 0.8;
    // console.log('粒子：', this.particles);
    this.particles.forEach((particle) => {
      const movetopos = $this.map2Pixel(particle.lng, particle.lat, particle);
      const linetopos = $this.map2Pixel(particle.tlng, particle.tlat, particle);
      $this.canvasContext.beginPath();
      if (movetopos != null && linetopos != null) {
        $this.canvasContext.moveTo(movetopos[0], movetopos[1]);
        $this.canvasContext.lineTo(linetopos[0], linetopos[1]);
        $this.canvasContext.strokeStyle = particle.color;
        $this.canvasContext.stroke();
      }
    });
  }
 
  /**
   * 随机数生成器（小数）
   */
  fRandomByfloat(min: number, max: number) {
    return min + Math.random() * (max - min);
  }
 
  /**
   * 随机数生成器（整数）
   */
  fRandomBy(min: number, max: number) {
    switch (arguments.length) {
      case 1:
        return parseInt((Math.random() * min + 1).toString(), 10);
      case 2:
        return parseInt((Math.random() * (max - min + 1) + min).toString(), 10);
      default:
        return 0;
    }
  }
 
  /**
   * 根据当前风场extent随机生成粒子
   */
  randomParticle(particle: CanvasParticle) {
    let safe = 30,
      x = -1,
      y = -1,
      lng = 0,
      lat = 0;
    const hasextent = this.extent.length != 0;
    const extent = hasextent ? this.extent : this.initExtent;
    const field = this.windField;
    do {
      try {
        if (hasextent) {
          const pos_x = this.fRandomBy(0, this.canvasWidth);
          const pos_y = this.fRandomBy(0, this.canvasHeight);
          const cartesian = this.viewer.camera.pickEllipsoid(
            new Cesium.Cartesian2(pos_x, pos_y),
            this.viewer.scene.globe.ellipsoid
          );
          if (!cartesian) return;
          const cartographic =
            this.viewer.scene.globe.ellipsoid.cartesianToCartographic(
              cartesian
            );
          if (cartographic) {
            lng = Cesium.Math.toDegrees(cartographic.longitude);
            lat = Cesium.Math.toDegrees(cartographic.latitude);
          }
        } else {
          lng = this.fRandomByfloat(extent[0], extent[1]);
          lat = this.fRandomByfloat(extent[2], extent[3]);
        }
      } catch (e) {
        console.error(e);
      }
      if (lng) {
        const gridpos = this.map2Grid(lng, lat);
        x = gridpos[0];
        y = gridpos[1];
      }
    } while (field.getUV(x, y) && field.getUV(x, y)[2] <= 0 && safe++ < 30);
    const uv = field.getUV(x, y);
    const nextLng = lng + this.calc_speedRate[0] * uv[0];
    const nextLat = lat + this.calc_speedRate[1] * uv[1];
    particle.lng = lng;
    particle.lat = lat;
    particle.x = x;
    particle.y = y;
    particle.tlng = nextLng;
    particle.tlat = nextLat;
    particle.speed = uv[2];
    particle.age = Math.round(Math.random() * this.maxAge); // 每一次生成都不一样
    particle.color = this.windyColor.getWindColor(particle.speed);
    return particle;
  }
  destroy() {
    this.canvasDom.destroy();
    this.hander.destroy();
    window.cancelAnimationFrame(this.animateFrame);
    this.isdistory = true;
  }
}